/** telnet Class, derived from the adobe flex documentation examples
 *  modifications by Jim Hayes (www.ifeedme.com), may 2007 
 * 
 */

package com.ifeedme.network.socket {
    import flash.events.*;   
    import flash.net.Socket;
    import flash.system.Security;
    import flash.utils.ByteArray;
    import flash.utils.setTimeout;
    import com.ifeedme.network.socket.events.SocketConnectionEvent;
    
        
    public class Telnet extends EventDispatcher
    {
        private const CR:int = 13; // Carriage Return (CR)
        private const WILL:int = 0xFB; // 251 - WILL (option code)
        private const WONT:int = 0xFC; // 252 - WON'T (option code)
        private const DO:int   = 0xFD; // 253 - DO (option code)
        private const DONT:int = 0xFE; // 254 - DON'T (option code)
        private const IAC:int  = 0xFF; // 255 - Interpret as Command (IAC)

        private var _serverURL:String;
        private var _portNumber:int;
        private var _socket:Socket;        
        private var _state:int = 0;
        
        public function Telnet(server:String, port:int) {
             
            _serverURL = server;
            _portNumber = port;
            
            
            // Create a new Socket object and assign event listeners.
            _socket = new Socket();
            _socket.addEventListener(Event.CONNECT, connectHandler);
            _socket.addEventListener(Event.CLOSE, closeHandler);
            _socket.addEventListener(ErrorEvent.ERROR, errorHandler);
            _socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            _socket.addEventListener(ProgressEvent.SOCKET_DATA, dataHandler);
            _socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            
            // Load policy file from remote server.
            Security.loadPolicyFile("http://" + _serverURL + "/crossdomain.xml");
            // Attempt to connect to remote socket server.
            try {
                msg("Trying to connect to " + _serverURL + ":" + _portNumber + "\n");
                _socket.connect(_serverURL, _portNumber);
            } catch (error:Error) {
                /*
                    Unable to connect to remote server, display error 
                    message and close connection.
                */
                msg(error.message + "\n");
                _socket.close();
            }
        }
        
        public function closeConnection():void
        {
        	if (_socket.connected)
        	{
	        	try
	        	{
	        		_socket.close();
	        	}
	        	catch(e:Error)
	        	{
	        		trace("Closing socket failed \n" + e.name + "\n" + e.message)
	        	}
	       }
        }
        
        /**
         * This method is called if the socket encounters an ioError event.
         */
        public function ioErrorHandler(event:IOErrorEvent):void {
            msg("Unable to connect: socket error.\n");
        }
        
        /**
         * This method is called by our application and is used to send data
         * to the server.
         */
        public function writeBytesToSocket(ba:ByteArray):void {
        	try
        	{
        		_socket.writeBytes(ba);
            	_socket.flush();
        	}
        	catch(error:Error)
        	{
        		trace("Could not send bytes :( \n" + "Error message :\n" + error.message);
        	}
            
        }
        
        private function connectHandler(event:Event):void {
            var isConnected:Boolean = false;
            var msg:String = "unset message in connectHandler";
            if (_socket.connected) {
            	msg = "connected OK";
                isConnected = true;
            } else {
                msg = "unable to connect\n";
            }
            var conEvent:SocketConnectionEvent = new SocketConnectionEvent("connectionEvent",isConnected,this._serverURL,this._portNumber,msg);
            this.dispatchEvent(conEvent);
        }
        
        private function securityErrorHandler(event:Event):void
        {
        	var isConnected:Boolean = false;
        	var msg:String = "Security Error";
        	var conEvent:SocketConnectionEvent = new SocketConnectionEvent("connectionEvent",isConnected,this._serverURL,this._portNumber,msg);
            this.dispatchEvent(conEvent);
        }
        
        /**
         * This method is called when the socket connection is closed by 
         * the server.
         */
        private function closeHandler(event:Event):void {
            var msg:String = "connection closed";
            var isConnected:Boolean = false;
            var conEvent:SocketConnectionEvent = new SocketConnectionEvent("connectionEvent",isConnected,this._serverURL,this._portNumber,msg);
            this.dispatchEvent(conEvent);
        }
        
        /**
         * This method is called if the socket throws an error.
         */
        private function errorHandler(event:ErrorEvent):void {            
            var msg:String = "Error : " + event.text;
            var isConnected:Boolean = false;
            var conEvent:SocketConnectionEvent = new SocketConnectionEvent("connectionEvent",isConnected,this._serverURL,this._portNumber,msg);
            this.dispatchEvent(conEvent);
        }
        
        /**
         * This method is called when the socket receives data from the server.
         */
        private function dataHandler(event:ProgressEvent):void {
            var n:int = _socket.bytesAvailable;
            // Loop through each available byte returned from the socket connection.
            while (--n >= 0) {
                // Read next available byte.
                var b:int = _socket.readUnsignedByte();
                switch (_state) {
                    case 0:
                        // If the current byte is the "Interpret as Command" code, set the state to 1.
                        if (b == IAC) {
                            _state = 1;
                        // Else, if the byte is not a carriage return, display the character using the msg() method.
                        } else if (b != CR) {
                            msg(String.fromCharCode(b));
                        }
                        break;
                    case 1:
                        // If the current byte is the "DO" code, set the state to 2.
                        if (b == DO) {
                            _state = 2;
                        } else {
                            _state = 0;
                        }
                        break;
                    // Blindly reject the option.
                    case 2:
                        /*
                            Write the "Interpret as Command" code, "WONT" code, 
                            and current byte to the socket and send the contents 
                            to the server by calling the flush() method.
                        */
                        _socket.writeByte(IAC);
                        _socket.writeByte(WONT);
                        _socket.writeByte(b);
                        _socket.flush();
                        _state = 0;
                        break;
                }
            }
        }
        
        
         private function msg(value:String):void {
            trace(value);
        } 
        
    }
}
